package net.cyndeline.rldungeon.dgs.graph

import scalax.collection.GraphEdge.{NodeProduct, EdgeCopy, ExtendedKey, UnDiEdge}
import scalax.collection.GraphPredef.OuterEdge
import net.cyndeline.rldungeon.dgs.objects.Responder
import net.cyndeline.rldungeon.dgs.EdgeCopyFactory

/**
 * A default edge representing a corridor in the map graph.
 *
 * Usage: implicit def edge2DGSEdgeAssoc[A <: Room](e: UnDiEdge[A]) = new DGSEdgeAssoc[A](e)
 *
 * @param responder The responder of this edge, if any. Otherwise None.
 * @param responderBottleneck True if moving from a room on one side of this edge to a room on the other side requires
 *                            the player to pass this edge. Otherwise false if the edge is a part of a cycle.
 */
class DGSEdge[N](nodes: Product, val responder: Option[Responder], responderBottleneck: Boolean)
  extends UnDiEdge[N](nodes)
  with ExtendedKey[N]
  with EdgeCopy[DGSEdge]
  with OuterEdge[N,DGSEdge]
  with DGSFunctionality[N, DGSEdge] {

  def keyAttributes = Seq(nodes, responder, responderBottleneck)

  override def copy[NN](newNodes: Product) = new DGSEdge[NN](newNodes, responder, responderBottleneck)

  /**
   * @return True if this edge must be traversed when moving from start to goal on a map.
   */
  def isBottleneck: Boolean = responderBottleneck

  /**
   * Sets this edges bottleneck status.
   * @param status True if this edge must be traversed when moving from start to goal, otherwise false.
   * @return A copy of the edge with its bottleneck status set.
   */
  def setBottleneckStatus(status: Boolean): DGSEdge[N] = new DGSEdge(nodes, responder, status)

  /**
   * @return The amount of additional responders this edge can host.
   */
  def remainingResponderCapacity: Int = if (responder.isDefined) 0 else 1

  /**
   * @param responders Collection of responders to add.
   * @tparam RType Type of responder being added.
   * @return A copy of the edge with the responders added.
   */
  def addResponder[RType <: Responder](responders: RType*): DGSEdge[N] = {
    val responderCapacity = remainingResponderCapacity
    if (responders.size > responderCapacity)
      throw new IllegalArgumentException("Attempted to add " + responders.size + " responder(s) to the edge " + this + ", but the current capacity is " + responderCapacity)
    else if (responders.isEmpty)
      throw new IllegalArgumentException("No responders specified.")

    new DGSEdge(nodes, Option(responders.head), responderBottleneck)
  }
}

object DGSEdge {
  def apply[T <: Room[T]](from: T, to: T) =
    new DGSEdge[T](NodeProduct(from, to), None, false)

  def apply[T <: Room[T]](from: T, to: T, respond: Option[Responder], canHaveResponders: Boolean) =
    new DGSEdge[T](NodeProduct(from, to), respond, canHaveResponders)
  def unapply[T <: Room[T]](e: DGSEdge[T]): Option[(T, T, Option[Responder])] =
    if (e eq null) None else Some(e._1, e._2, e.responder)
}

final class DGSEdgeAssoc[A <: Room[A]](val e: UnDiEdge[A]) {

  @inline def dgs_## (responder: Option[Responder], canHaveResponders: Boolean) =
    new DGSEdge[A](e.nodes, responder, canHaveResponders) with OuterEdge[A,DGSEdge]

  @inline def dgs_empty () =
    new DGSEdge[A](e.nodes, None, false) with OuterEdge[A,DGSEdge]
}

final class DGSEdgeFactory[VType <: Room[VType]] extends EdgeCopyFactory[VType, DGSEdge] {
  def copyEdge(edge: DGSEdge[VType], a: VType, b: VType): DGSEdge[VType] with OuterEdge[VType, DGSEdge] = DGSEdge(a, b, edge.responder, edge.isBottleneck)
}
